package model.map;

/**
 * Class defining points on the game map.  
 * Note: Assume that the second point system is being used (see hexOption2.png).
 * @author Zachary
 *
 */
public class MapPoint implements Cloneable{
	/**
	 * Define direction MapPoints.
	 */
	public static final MapPoint NORTH = new MapPoint(-2,0);
	public static final MapPoint SOUTH = new MapPoint(2,0);
	public static final MapPoint NORTHWEST = new MapPoint(-1,-1);
	public static final MapPoint NORTHEAST = new MapPoint(-1,1);
	public static final MapPoint SOUTHWEST = new MapPoint(1,-1);
	public static final MapPoint SOUTHEAST = new MapPoint(1,1);
	
	public static final int NUMDIRECTIONS = 6;
	
	private int xCoordinate;
	private int yCoordinate;
	
	/**
	 * Constructor for MapPoint.
	 * @param x
	 * @param y
	 */
	public MapPoint(int x, int y){
		this.xCoordinate = x;
		this.yCoordinate = y;
	}
	
	/**
	 * Constructor for MapPoint.
	 * @param string
	 */
	public MapPoint(String string){
		String[] nums = string.split("[(,)]");
		this.xCoordinate = Integer.parseInt(nums[0]);
		this.yCoordinate = Integer.parseInt(nums[1]);
	}
	
	public int getXCoordinate(){
		return xCoordinate;
	}
	
	public int getYCoordinate(){
		return yCoordinate;
	}
	
	/**
	 * Add two MapPoints.
	 */
	public static MapPoint addPoints(MapPoint a, MapPoint b){
		return new MapPoint(a.getXCoordinate() + b.getXCoordinate(), 
				a.getYCoordinate() + b.getYCoordinate());
	}
	
	/**
	 * subtract two MapPoints
	 */
	public static MapPoint subtractPoints(MapPoint a, MapPoint b){
		return new MapPoint(a.getXCoordinate() - b.getXCoordinate(), 
				a.getYCoordinate() - b.getYCoordinate());
	}
	
	/**
	 * Find the distance between the two MapPoints based 
	 * on the hex grid metric.
	 * @param a
	 * @param b
	 * @return
	 */
	public static int findDistance(MapPoint a, MapPoint b){
		double xDist = Math.abs(a.xCoordinate - b.xCoordinate);
		double yDist = Math.abs(a.yCoordinate - b.yCoordinate);
		return Math.max((int)((xDist + yDist)/2 + .5), (int)Math.sqrt(xDist * xDist + yDist * yDist));
	}
	
	/**
	 * Tests if a MapPoint is a valid direction.
	 */
	public static boolean isValidDirection(MapPoint p){
		return p.equals(NORTH) || p.equals(SOUTH) || p.equals(NORTHEAST) ||
		p.equals(NORTHWEST) || p.equals(SOUTHEAST) || p.equals(SOUTHWEST);  
	}
	
	/**
	 * Tests if a MapPOint is a valid location.
	 * valid location <=> point.x and point.y are both even or odd
	 */
	public static boolean isValidLocation(MapPoint p){
		return (p.xCoordinate + p.yCoordinate) % 2 == 0;
	}
	
	/**
	 * Return the opposite direction of the given direction.
	 * @param d
	 * @return
	 */
	public static MapPoint getOppositeDirection(MapPoint d){
		return new MapPoint(-1 * d.xCoordinate, -1*d.yCoordinate);
	}
	
	/**
	 * Return the next direction when moving clockwise.
	 * @param d
	 * @return
	 */
	public static MapPoint getNextClockWiseDirection(MapPoint d){
		MapPoint ret = null;
		if(d.equals(NORTH))
			ret = NORTHEAST;
		else if(d.equals(SOUTH))
			ret = SOUTHWEST;
		else if(d.equals(NORTHEAST))
			ret = SOUTHEAST;
		else if(d.equals(SOUTHWEST))
			ret = NORTHWEST;
		else if(d.equals(NORTHWEST))
			ret = NORTH;
		else if(d.equals(SOUTHEAST))
			ret = SOUTH;
		else
			ret = new MapPoint(0,0);
		return ret;
	}
	
	/**
	 * Return the next direction when moving clockwise.
	 * @param d
	 * @return
	 */
	public static MapPoint getNextCounterClockWiseDirection(MapPoint d){
		MapPoint ret = null;
		if(d.equals(NORTH))
			ret = NORTHWEST;
		else if(d.equals(SOUTH))
			ret = SOUTHEAST;
		else if(d.equals(NORTHEAST))
			ret = NORTH;
		else if(d.equals(SOUTHWEST))
			ret = SOUTH;
		else if(d.equals(NORTHWEST))
			ret = SOUTHWEST;
		else if(d.equals(SOUTHEAST))
			ret = NORTHEAST;
		else
			ret = new MapPoint(0,0);
		return ret;
	}
	
	/**
	 * Modifies direction.
	 * @param d
	 * @return
	 */
	public static MapPoint getOutRight(MapPoint d){
		MapPoint ret = null;
		if(d.equals(NORTH))
			ret = SOUTHEAST;
		else if(d.equals(SOUTH))
			ret = NORTHWEST;
		else if(d.equals(NORTHEAST))
			ret = SOUTH;
		else if(d.equals(SOUTHWEST))
			ret = NORTH;
		else if(d.equals(NORTHWEST))
			ret = NORTHEAST;
		else if(d.equals(SOUTHEAST))
			ret = SOUTHWEST;
		else
			ret = new MapPoint(0,0);
		return ret;
	}
	
	/**
	 * Modifies direction.
	 * @param d
	 * @return
	 */
	public static MapPoint getOutLeft(MapPoint d){
		MapPoint ret = null;
		if(d.equals(NORTH))
			ret = SOUTHWEST;
		else if(d.equals(SOUTH))
			ret = NORTHEAST;
		else if(d.equals(NORTHEAST))
			ret = NORTHWEST;
		else if(d.equals(SOUTHWEST))
			ret = SOUTHEAST;
		else if(d.equals(NORTHWEST))
			ret = SOUTH;
		else if(d.equals(SOUTHEAST))
			ret = NORTH;
		else
			ret = new MapPoint(0,0);
		return ret;
	}
	
	/**
	 * Scale a direction vector.
	 * @param direction
	 * @param factor
	 * @return
	 */
	public static MapPoint scaleDirection(MapPoint direction, int factor){
		return new MapPoint(direction.xCoordinate * factor, direction.yCoordinate * factor);
	}
	
	/**
	 * Approximates the direction represented by an arbitrary point.
	 * @param point
	 * @return
	 */
	public MapPoint approximateDirection(MapPoint point){
		MapPoint[] tests = new MapPoint[6];
		tests[0] = MapPoint.addPoints(NORTH, point);
		tests[1] = MapPoint.addPoints(SOUTH, point);
		tests[2] = MapPoint.addPoints(NORTHEAST, point);
		tests[3] = MapPoint.addPoints(NORTHWEST, point);
		tests[4] = MapPoint.addPoints(SOUTHEAST, point);
		tests[5] = MapPoint.addPoints(SOUTHWEST, point);
		
		MapPoint smallest = new MapPoint(Integer.MAX_VALUE, Integer.MAX_VALUE);
		for(int i = 0;i<tests.length;i++)
			if(findMagnitude(tests[i]) < findMagnitude(smallest))
				smallest = tests[i];
		
		MapPoint ret = new MapPoint(0,0);
		if(smallest.equals(tests[0]))
			ret = SOUTH;
		else if(smallest.equals(tests[1]))
			ret = NORTH;
		else if(smallest.equals(tests[2]))
			ret = SOUTHWEST;
		else if(smallest.equals(tests[3]))
			ret = SOUTHEAST;
		else if(smallest.equals(tests[4]))
			ret = NORTHWEST;
		else if(smallest.equals(tests[5]))
			ret = NORTHEAST;
		
		return ret;
	}
	
	/**
	 * Finds the magnitude of the point.
	 * @param point
	 * @return
	 */
	private int findMagnitude(MapPoint point){
		return findDistance(point, new MapPoint(0,0));
	}
	
	/**
	 * Implement Cloneable interface.
	 */
	public MapPoint clone(){
		try{
		      MapPoint cloned = (MapPoint)super.clone();
		      return cloned;
	    }catch(CloneNotSupportedException e){
		      System.out.println(e);
		      return null;
	    }
	}
	
	/**
	 * Return a string representation of the point.
	 */
	public String toString(){
		return "(" + this.xCoordinate + "," + this.yCoordinate + ")";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + xCoordinate;
		result = prime * result + yCoordinate;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MapPoint other = (MapPoint) obj;
		if (xCoordinate != other.xCoordinate)
			return false;
		if (yCoordinate != other.yCoordinate)
			return false;
		return true;
	}
}